package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldSort;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Buckets;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.aggregations.TopHitsAggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.*;
import co.elastic.clients.json.JsonData;
import com.alibaba.nacos.shaded.com.google.common.collect.Lists;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.search.HomePage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.naming.directory.SearchResult;
import java.io.IOException;
import java.lang.reflect.Array;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import java.util.stream.Stream;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SearchServiceImpl implements SearchService {
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Resource
    private UserInfoFeignClient userInfoFeignClient;

    @Resource
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Resource
    private CategoryFeignClient categoryFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ExecutorService executorService;

    @Override
    public void upAlbum(Long albumId) {
        Result<AlbumInfo> albumInfos = albumInfoFeignClient.getAlbumInfo(albumId);
        Assert.notNull(albumInfos, "索引库新增或修改时：获取专辑信息失败！");
        AlbumInfo albumInfo = albumInfos.getData();
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        BeanUtils.copyProperties(albumInfo, albumInfoIndex);
        //2.根据albumId查询统计表√
        Result<AlbumStatVo> albumStat = albumInfoFeignClient.findAlbumStat(albumInfo.getId());
        Assert.notNull(albumStat, "获取数据时：根据albumId查询统计表失败！");
        if (albumStat != null) {
            AlbumStatVo albumStatVo = albumStat.getData();
            albumInfoIndex.setPlayStatNum(albumStatVo.getPlayStatNum());
            albumInfoIndex.setSubscribeStatNum(albumStatVo.getSubscribeStatNum());
            albumInfoIndex.setCommentStatNum(albumStatVo.getCommentStatNum());
            albumInfoIndex.setBuyStatNum(albumStatVo.getBuyStatNum());
            albumInfoIndex.setHotScore(albumStatVo.getPlayStatNum() * 0.1 + albumStatVo.getSubscribeStatNum() * 0.2 + albumStatVo.getCommentStatNum() * 0.4 + albumStatVo.getBuyStatNum() * 0.3);
        }
        //3.根据userId查询用户信息√
        Result<UserInfo> userById = userInfoFeignClient.findUserById(albumInfo.getUserId());
        Assert.notNull(userById, "获取数据时：根据id用户信息失败!");
        UserInfo user = userById.getData();
        if (user != null) {
            albumInfoIndex.setAnnouncerName(user.getNickname());
        }
        //4.根据category3Id查询三级分类id
        Result<BaseCategoryView> categories = categoryFeignClient.findCategories(albumInfo.getCategory3Id());
        Assert.notNull(categories, "查询数据时：根据category3Id查询三级分类id失败！");
        BaseCategoryView baseCategoryView = categories.getData();
        if (baseCategoryView != null) {
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
            albumInfoIndex.setCategory3Id(baseCategoryView.getCategory3Id());
        }
        //5.查询标签信息√
        Result<List<AlbumAttributeValue>> attributes = albumInfoFeignClient.findAttributes(albumInfo.getId());
        Assert.notNull(attributes, "获取数据时：查询标签信息失败！");
        List<AlbumAttributeValue> attributeList = attributes.getData();
        if (!CollectionUtils.isEmpty(attributeList)) {
            List<AttributeValueIndex> attributeValueIndexList = attributeList.stream().map(albumAttributeValue -> {
                AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
                attributeValueIndex.setValueId(albumAttributeValue.getValueId());
                return attributeValueIndex;
            }).collect(Collectors.toList());
            albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
        }
        elasticsearchTemplate.save(albumInfoIndex);

        //新增更改时设置suggestifo库
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(albumInfo.getId().toString());
        suggestIndex.setTitle(albumInfo.getAlbumTitle());
        suggestIndex.setKeyword(new Completion(Arrays.asList(albumInfo.getAlbumTitle())));
        suggestIndex.setKeywordPinyin(new Completion(Arrays.asList(PinYinUtils.toHanyuPinyin(albumInfo.getAlbumTitle()))));
        suggestIndex.setKeywordSequence(new Completion(Arrays.asList(PinYinUtils.getFirstLetter(albumInfo.getAlbumTitle()))));
        //保存到es
        this.elasticsearchTemplate.save(suggestIndex);
        //保存主播信息
        Result<UserInfo> userInfoResult = userInfoFeignClient.findUserById(albumInfo.getUserId());
        Assert.notNull(userInfoResult, "获取主播信息失败！");
        UserInfo userInfo = userInfoResult.getData();
        Assert.notNull(userInfoResult, "获取主播信息失败！");
        suggestIndex.setId("anchor" + albumInfo.getUserId());
        suggestIndex.setTitle(userInfo.getNickname());
        suggestIndex.setKeyword(new Completion(Arrays.asList(userInfo.getNickname())));
        suggestIndex.setKeywordPinyin(new Completion(Arrays.asList(PinYinUtils.toHanyuPinyin(userInfo.getNickname()))));
        suggestIndex.setKeywordSequence(new Completion(Arrays.asList(PinYinUtils.getFirstLetter(userInfo.getNickname()))));
        //保存到es
        this.elasticsearchTemplate.save(suggestIndex);
    }

    @Override
    public void lowAlbum(String albumId) {
        //删除albuminfo库
        elasticsearchTemplate.delete(albumId, AlbumInfoIndex.class);
        //删除suggestinfo库
//        //删除主播可以不用删
//        Result<UserInfo> userInfoResult = userInfoFeignClient.findUserById(Long.valueOf(albumId));
//        Assert.notNull(userInfoResult, "获取用户信息失败");
//        UserInfo userInfo = userInfoResult.getData();
//        if (userInfo != null) {
//            elasticsearchTemplate.delete("anchor" + userInfo.getId(), SuggestIndex.class);
//        }
        //删除专辑的关键词或者拼音或者简写
        elasticsearchTemplate.delete(albumId, SuggestIndex.class);
    }

    @Override
    public List<HomePage> channel(Long category1Id) {
        //根据category1Id查询category3Id
        Result<List<BaseCategory3>> topBaseCategory3 = categoryFeignClient.findTopBaseCategory3(category1Id);
        Assert.notNull(topBaseCategory3, "根据categoryId查询top榜失败！");
        List<BaseCategory3> baseCategory3List = topBaseCategory3.getData();
        if (CollectionUtils.isEmpty(baseCategory3List)) {
            return null;
        }
        //list转换成map
        Map<Long, BaseCategory3> map = baseCategory3List.stream().collect(Collectors.toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));
        //转换baseCategory3List
        List<FieldValue> fieldValueList = baseCategory3List.stream().map(baseCategory3 -> {
            FieldValue fieldValue = FieldValue.of(baseCategory3.getId());
            return fieldValue;
        }).collect(Collectors.toList());
        //搜索数据
        try {
            //创建searchRequest对象
            SearchRequest request = SearchRequest.of((s -> s
                    .index("albuminfo")
                    .size(0)
                    .query(ms -> ms.terms(tr -> tr.field("category3Id").terms(ts -> ts.value(fieldValueList))))
                    .aggregations("cateAgg", f -> f.terms(t -> t.field("category3Id"))
                            .aggregations("hotTop", ht -> ht.topHits(th -> th.size(6).sort(st -> st.field(fs -> fs.field("hotScore").order(SortOrder.Desc))))))));
            SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(request, AlbumInfoIndex.class);
            //分析结果集
            System.out.println(request.toString());
            Aggregate cateAgg = response.aggregations().get("cateAgg");
            List<LongTermsBucket> buckets = cateAgg.lterms().buckets().array();
            return buckets.stream().map(bucket -> {
                HomePage homePage = new HomePage();
                //获取三级分类Id
                long category3Id = bucket.key();
                //设置BaseCategory3对象
                homePage.setBaseCategory3(map.get(category3Id));
                //子聚合数据
                Map<String, Aggregate> aggregations = bucket.aggregations();
                if (CollectionUtils.isEmpty(aggregations)) {
                    return homePage;
                }
                Aggregate hotScore = aggregations.get("hotTop");
                TopHitsAggregate topHitsAggregate = hotScore.topHits();
                HitsMetadata<JsonData> hits = topHitsAggregate.hits();
                if (hits == null) {
                    return homePage;
                }
                List<Hit<JsonData>> hitList = hits.hits();
                if (CollectionUtils.isEmpty(hitList)) {
                    return homePage;
                }
                homePage.setList(hitList.stream().map(hit -> hit.source().to(AlbumInfoIndex.class)).collect(Collectors.toList()));
                return homePage;
            }).collect(Collectors.toList());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        try {
            SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(getSearchRequest(albumIndexQuery), AlbumInfoIndex.class);
            //解析结果
            AlbumSearchResponseVo responseVo = parseResult(response, albumIndexQuery);
            return responseVo;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //构建searchRequest对象方法
    private SearchRequest getSearchRequest(AlbumIndexQuery albumIndexQuery) {
        //创建SearchRequest的构造器，并指定索引库
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index("albuminfo");
        //创建bool构建器
        BoolQuery.Builder boolBuilder = new BoolQuery.Builder();
        //关键字匹配
        String keyword = albumIndexQuery.getKeyword();
        if (StringUtils.isNotBlank(keyword)) {
            boolBuilder.must(m -> m.multiMatch(mm -> mm.query(keyword).fields("albumTitle", "albumIntro", "announcerName").operator(Operator.And)));
        }
        //三级id匹配
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            boolBuilder.filter(f -> f.term(t -> t.field("category1Id").value(category1Id)));
        }
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            boolBuilder.filter(f -> f.term(t -> t.field("category2Id").value(category2Id)));
        }
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            boolBuilder.filter(f -> f.term(t -> t.field("category3Id").value(category3Id)));
        }
        //标签嵌套的过滤["2:3", "3:6", "4:8"]
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)) {
            attributeList.forEach(attribute -> {
                //attributeId和valueId
                String[] attrs = StringUtils.split(attribute, ":");
                if (attrs != null && attrs.length == 2 && StringUtils.isNumeric(attrs[0]) && StringUtils.isNumeric(attrs[1])) {
                    boolBuilder.filter(f -> f.nested(n -> n.path("attributeValueIndexList")
                            .query(q -> q.bool(b -> b
                                    .must(m -> m.term(t -> t.field("attributeId").value(attrs[0])))
                                    .must(m -> m.term(t -> t.field("valueId").value(attrs[0]))))
                            )));
                }
            });
        }
        //构建查询构造器
        builder.query(boolBuilder.build()._toQuery());
        //排序匹配
        String order = albumIndexQuery.getOrder();
        if (StringUtils.isNotBlank(order)) {
            String[] orders = StringUtils.split(order, ":");
            List<String> sorts = Arrays.asList("asc", "desc");
            if (orders != null && orders.length == 2 && sorts.contains(orders[1])) ;
            String fieldName = null;
            switch (orders[0]) {
                case "1":
                    fieldName = "hotScore";
                    break;
                case "2":
                    fieldName = "playStatNum";
                    break;
                case "3":
                    fieldName = "createTime";
                    break;
            }
            String finalFieldName = fieldName;
            builder.sort(s -> s.field(f -> f.field(finalFieldName).order(StringUtils.equals(orders[1], "desc") ? SortOrder.Desc : SortOrder.Asc)));
        }
        //构建分页
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        builder.from((pageNo - 1) * pageSize);
        builder.size(pageSize);
        //高亮
        builder.highlight(h -> h.fields("albumTitle", f -> f
                        .preTags("<font style='color:red'>")
                        .postTags("</font>"))
                .fields("albumIntro", f -> f
                        .preTags("<font style='color:red'>")
                        .postTags("</font>")));
        //结果集过滤
        builder.source(s -> s.filter(f -> f.excludes("createTime")));
        //构建request
        SearchRequest request = builder.build();
        System.out.println(request);
        return request;
    }

    //解析结果方法
    private AlbumSearchResponseVo parseResult(SearchResponse<AlbumInfoIndex> response, AlbumIndexQuery albumIndexQuery) {
        AlbumSearchResponseVo responseVo = new AlbumSearchResponseVo();
        //解析结果集
        HitsMetadata<AlbumInfoIndex> hits = response.hits();
        if (hits == null) {
            return responseVo;
        }
        List<Hit<AlbumInfoIndex>> hitsList = hits.hits();
        if (CollectionUtils.isEmpty(hitsList)) {
            return responseVo;
        }
        //设置商品列表
        List<AlbumInfoIndexVo> list = new ArrayList<>();
        hitsList.forEach(hit -> {
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            AlbumInfoIndex albumInfoIndex = hit.source();
            Map<String, List<String>> highlightMap = hit.highlight();
            if (!CollectionUtils.isEmpty(highlightMap) && !CollectionUtils.isEmpty(highlightMap.get("albumTitle"))) {
                String albumTitle = highlightMap.get("albumTitle").get(0);
                albumInfoIndex.setAlbumTitle(albumTitle);
            }
            BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
            list.add(albumInfoIndexVo);
        });
        responseVo.setList(list);
        //总记录数
        long total = hits.total().value();
        responseVo.setTotal(total);
        //页大小
        Integer pageSize = albumIndexQuery.getPageSize();
        responseVo.setPageSize(pageSize);
        //当前页码
        Integer pageNo = albumIndexQuery.getPageNo();
        responseVo.setPageNo(pageNo);
        //总页数
        Long totalPages = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        responseVo.setTotalPages(totalPages);
        return responseVo;
    }

    @Override
    public List<AlbumInfoIndexVo> findRankingList(Long category1Id, String sortFiled) {
        try {
            SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(s -> s.index("albuminfo")
                            .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                            .size(20)
                            .sort(st -> st.field(f -> f.field(sortFiled).order(SortOrder.Desc)))
                    , AlbumInfoIndex.class);
            //解析结果集
            List<Hit<AlbumInfoIndex>> hits = response.hits().hits();
            return hits.stream().map(hit -> {
                AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
                AlbumInfoIndex albumInfoIndex = hit.source();
                BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
                return albumInfoIndexVo;
            }).collect(Collectors.toList());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<String> completeSuggest(String prefix) {
        //准备返回集合
        List<String> suggests = new ArrayList<>();
        //搜索索引库
        try {
            SearchRequest request = SearchRequest.of(s -> s.index("suggestinfo")
                    .suggest(st -> st
                            .suggesters("keywordSgg", sr -> sr
                                    .prefix(prefix).completion(c -> c.field("keyword")
                                            .size(10)
                                            .skipDuplicates(true)))
                            .suggesters("keywordPYSgg", sr -> sr
                                    .prefix(prefix).completion(c -> c.field("keywordPinyin")
                                            .size(10)
                                            .skipDuplicates(true)))
                            .suggesters("keywordSQSgg", sr -> sr
                                    .prefix(prefix).completion(c -> c.field("keywordSequence")
                                            .size(10)
                                            .skipDuplicates(true)))));
            System.out.println(request);
            SearchResponse<SuggestIndex> response = this.elasticsearchClient.search(request, SuggestIndex.class);
            //解析结果集
            Map<String, List<Suggestion<SuggestIndex>>> suggestMap = response.suggest();
            Suggestion<SuggestIndex> keywordSgg = suggestMap.get("keywordSgg").get(0);
            Suggestion<SuggestIndex> keywordPYSgg = suggestMap.get("keywordPYSgg").get(0);
            Suggestion<SuggestIndex> keywordSQSgg = suggestMap.get("keywordSQSgg").get(0);
            //提示词集合
            List<String> keywordSggs = getTitle(keywordSgg);
            List<String> keywordPYSggs = getTitle(keywordPYSgg);
            List<String> keywordSQSggs = getTitle(keywordSQSgg);
            //提示去重
            HashSet<String> keywords = new HashSet<>();
            keywords.addAll(keywordSggs);
            keywords.addAll(keywordPYSggs);
            keywords.addAll(keywordSQSggs);
            //将去重的提示词放入返回结果
            suggests.addAll(keywords);
            if (suggests.size() >= 10) {
                return suggests;
            }
            //提示词小于10个
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(s -> s.index("albuminfo")
                            .query(q -> q.fuzzy(f -> f.field("albumTitle").value(prefix).fuzziness("2")))
                    , AlbumInfoIndex.class);
            //解析模糊匹配的结果集
            List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
            hits.forEach(hit -> {
                if (suggests.size() >= 10) {
                    return;
                }
                suggests.add(hit.source().getAlbumTitle());
            });
            return suggests;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private List<String> getTitle(Suggestion<SuggestIndex> suggestions) {
        List<CompletionSuggestOption<SuggestIndex>> options = suggestions.completion().options();
        List<String> suggest = options.stream().map(option -> option.source().getTitle()).collect(Collectors.toList());
        return suggest;
    }

    @Override
    public void updateTop() {
        //起始时间
        String startTime = (String) this.redisTemplate.opsForValue().get(RedisConstant.ALBUM_STAT_ENDTIME);
        if (StringUtils.isBlank(startTime)) {
            startTime = "2024-03-05 11:23:35";
        }
        //截止时间
        String endTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        //将上一次的截止时间存入Redis 作为下一次的起始时间
        this.redisTemplate.opsForValue().set(RedisConstant.ALBUM_STAT_ENDTIME, endTime);
        //查询需要更新的ids
        Result<List<Long>> idsResult = albumInfoFeignClient.getChangedAlbumIds(startTime, endTime);
        Assert.notNull(idsResult, "更新top榜：获取id集合失败！");
        List<Long> ids = idsResult.getData();
        if (CollectionUtils.isEmpty(ids)) {
            return;
        }

        //将统计信息更新到es里面
        List<List<Long>> partition = Lists.partition(ids, 5000);
        partition.forEach(subIds -> {
            executorService.execute(() -> {
                //查询需要更新的状态集合
                Result<List<AlbumStatVo>> albumStatVoResult = albumInfoFeignClient.getStatByIds(subIds);
                Assert.notNull(albumStatVoResult, "更新top榜：获取统计信息失败！");
                List<AlbumStatVo> albumStatVos = albumStatVoResult.getData();
                if (CollectionUtils.isEmpty(albumStatVos)) {
                    return;
                }
                albumStatVos.forEach(albumStatVo -> {
                    AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
                    BeanUtils.copyProperties(albumStatVo, albumInfoIndex);
                    albumInfoIndex.setId(albumStatVo.getAlbumId());
                    //设置hotScore
                    albumInfoIndex.setHotScore(albumStatVo.getPlayStatNum() * 0.1 + albumStatVo.getSubscribeStatNum() * 0.2 + albumStatVo.getCommentStatNum() * 0.4 + albumStatVo.getBuyStatNum() * 0.3);
                    this.elasticsearchTemplate.update(albumInfoIndex);
                });
            });
        });
    }
}
